/*
 * @Author: xiangru.xiao xiangru.xiao@mthreads.com
 * @Date: 2023-05-06 14:28:13
 * @LastEditors: xiangru.xiao xiangru.xiao@mthreads.com
 * @LastEditTime: 2023-05-24 17:00:37
 * Copyright (c) 2023 by Moore Threads, All Rights Reserved. 
 */
#include <iostream>
#include "objectfactory.h"
#include <string>
#include <vector>
#include "node.h"
#include "cmdline.h"
#include "Log.h"
#include "cu_helper.h"
#include "score.h"
#include <fstream>

std::vector<Node *> sceneList;
std::map<std::string, int> weightMap;
void printfScore(const CudaScore &score);

void initWeight()
{
    std::ifstream stream("../media/weight.txt");
    std::string line;

    if (stream) {
        while (getline(stream, line)) {
            if (!line.empty() && line[0] != '#') {
                std::string name;
                int weight;
                std::stringstream ss(line);
                ss >> name >> weight;
                weightMap.insert(std::make_pair(name, weight));
            }
        }
    }
}

void setreference(CudaScore &score)
{
    std::ifstream stream("../media/reference.txt");
    std::string line;

    if (stream) {
        while (getline(stream, line)) {
            if (!line.empty() && line[0] != '#') {
                unsigned int id;
                double refvalue = 10;
                unsigned long refrenceScore = 10000;
                std::stringstream ss(line);
                ss >> id >> refvalue >> refrenceScore;
                score.registerWeight((cu_test_type)id, refvalue, refrenceScore);
            }
        }
    }
}

void release()
{
    std::for_each(sceneList.begin(), sceneList.end(), [](Node * node) {
        delete node;
    });
    sceneList.clear();
    ObjectFactory<Node, std::string>::shutdown();
}

bool loadScenes()
{
    bool bList = false;
    if (ObjectFactory<Node, std::string>::creator == nullptr)
        return false;
    auto itr = ObjectFactory<Node, std::string>::creator->begin();
    while (itr != ObjectFactory<Node, std::string>::creator->end()) {
        Node *node = ObjectFactory<Node, std::string>::create(itr->first);
        if (node) {
            auto weight_itr = weightMap.find(node->name());
            if (weight_itr != weightMap.end())
                node->setWeightValue(weight_itr->second);
            sceneList.push_back(node);
        }
        itr ++;
        bList = true;
    }
    return bList;
}

void listScene()
{
    std::vector<Node *>::iterator it;
    for (it = sceneList.begin(); it != sceneList.end(); it++) {
        Log::info("[ %s ]  %s\n", (*it)->name().data(), (*it)->describe().data());
    }
}

std::vector<std::string> initRunnableScenes(std::vector<std::string> &benchmark)
{
    std::vector<std::string> scenes;
    if (benchmark.empty()) {
        std::vector<Node *>::iterator it;
        for (it = sceneList.begin(); it != sceneList.end(); it++) {
            scenes.emplace_back((*it)->name());
        }
    } else
        scenes = benchmark;;

    return scenes;
}

Node *scenes(const std::string &id)
{
    std::vector<Node *>::iterator it;
    for (it = sceneList.begin(); it != sceneList.end(); it++) {
        if (id == (*it)->name())
            return *it;
    }
    return nullptr;
}

bool isvalidScene(const std::vector<std::string> &benchmark, std::string &invalidname)
{
    std::vector<std::string> tempNames;
    std::vector<Node *>::iterator it;
    for (it = sceneList.begin(); it != sceneList.end(); it++) {
        tempNames.emplace_back((*it)->name());
    }
    for (int i = 0; i < benchmark.size(); ++i) {
        auto find = std::find(tempNames.begin(), tempNames.end(), benchmark[i]);
        if (find == tempNames.end()) {
            invalidname =  benchmark[i];
            return false;
        }
    }
    return true;
}

std::vector<std::string> sort(const std::vector<std::string> &vec)
{
    static std::string scenelist[] = {
        "maxhalf-compute",
        "maxint-compute",
        "maxsp-compute",
        "maxdp-compute",
        "bs-download-speed",
        "bs-readback-speed",
        "gmem-rw-bandwidth",
        "fft-test",
        "gemm-test"
    };
    std::vector<std::string> list;
    for (int i = 0; i < sizeof(scenelist) / sizeof(scenelist[0]); ++i) {

        for (int j = 0; j < vec.size(); ++j) {
            if (scenelist[i] == vec[j]) {
                list.push_back(scenelist[i]);
                break;
            }
        }
    }
    return list;
}

#define PRINTHEAD()\
    Log::startwrite();\
    Log::info("%s GPU-perf-cuda %s\n", std::string(30, '=').c_str(), std::string(30, '=').c_str());

#define PRINTEND()\
    Log::info("%s=============%s\n", std::string(30, '=').c_str(), std::string(30, '=').c_str());\
    Log::writeend();

#define PRINTSPLITLINE()\
    Log::info("%s=============%s\n", std::string(30, '=').c_str(), std::string(30, '=').c_str());

int main(int argc, char *argv[])
{
    PRINTHEAD();
    int deviceNum = 0;
    CudaScore score;
    setreference(score);
    initWeight();
    if (!loadScenes()) {
        Log::error("No scene.\n");
        release();
        PRINTEND();
        return 0;
    }

    cmdline::parser parser;
    parser.set_program_name("GPU-perf-cuda");
    parser.add<std::string>("benchmark", 'b', "A benchmark or options to run: '-b maxdp-compute-cuda'",
                            false);
    parser.add<int>("device", 'd', "device number", false, 0, cmdline::range(0, 256));

    parser.add("help", 'h', "Display help");
    parser.add("list-scenes", 'l', "Display information about the available scenes");
    parser.parse_check(argc, argv);

    if (parser.exist("list-scenes")) {
        listScene();
        release();
        PRINTEND();
        return 0;
    }
    std::vector<std::string> benchmarks;

    if (parser.exist("benchmark")) {
        benchmarks.emplace_back(parser.get<std::string>("benchmark"));
        std::string invalidname;
        if (!isvalidScene(benchmarks, invalidname)) {
            Log::error("invalid sence name: \"%s\" \n", invalidname.data());
            Log::error("please type GPU-perf-cuda --list-scene display registered scenes\n");
            release();
            PRINTEND();
            return 0;
        }
    }

    if (parser.exist("device")) {
        deviceNum = parser.get<int>("device");
    }

    // print device info
    CUDADeviceInfo devinfo(deviceNum);
    Log::info(devinfo.infoString().c_str());
    PRINTSPLITLINE();

    std::vector<std::string> runnablescenes = initRunnableScenes(benchmarks);
    runnablescenes = sort(runnablescenes);
    bool flag = false;
    Log::info("%-18s| %-15s| %-8s | %-8s | %-8s  |  %s \n", "Case", "type", "Weight", "Value", "Unit",
        "Result");
    for (int i = 0; i < runnablescenes.size(); ++i) {
        auto current = scenes(runnablescenes[i]);
        current->setDevice(&devinfo);
        if (current->ignore()) continue;
        int weight = current->weightValue();
        std::string typeName = Node::nodeTypeName(current->nodeType());
	// cancel running status print for capture test results
	/*
        Log::info("%-18s| %-21s| %-8d | %-8s | %-8s  |  %-5s \n",
            runnablescenes[i].data(),
            typeName.data(),
            weight,
            "",
            current->unit().c_str(),
            "Runing");
	*/
        // runing
        auto result  = current->run();

        if (result == Node::RunningState_Success) {
            // clearlastline("\r");
            Log::info("%-18s| %-21s| %-8d | %-8.2f | %-8s  |  %-5s \n", runnablescenes[i].data(),
                typeName.data(), weight,
                current->performValue(), current->unit().c_str(),
                "success");
            score.setTesterScore(current->nodeType(), current->performValue(), current->weightValue());
        } else
            Log::info("%-18s| %-21s| %-8d | %-8.2f | %-8s  |  %-5s \n", runnablescenes[i].data(),
                typeName.data(), weight,
                current->performValue(), current->unit().c_str(),
                "FAILED");
    }
    PRINTSPLITLINE();
    printfScore(score);
    release();
    PRINTEND();
    return 0;
}

void printfScore(const CudaScore &score)
{
    auto itr =  score.testScore().begin();
    while (itr != score.testScore().end()) {
        auto type = itr->first;
        int weight = itr->second.weight;
        double value = itr->second.value / (double)(weight);

        std::string typeName = Node::nodeTypeName((Node::NodeType)type);
        Log::info("  %-20s:  %-15d   \n", typeName.c_str(), (unsigned long)value);
        itr++;
    }
    // total score
    // Log::info("  总分:    %-15d\n", score.totalScore());
    fprintf(stderr, "COUNT|%-15d|0|score\n", score.totalScore());

}

